#include<Arduino.h>

// 定义脉冲输入引脚
#define PWM_INPUT_PIN 5

// 用于存储计算结果的 volatile 变量
// volatile 关键字非常重要，它告诉编译器这些变量可能在任何时刻被中断改变，防止编译器进行错误的优化
volatile unsigned long pulseHighTime = 0;  // 脉宽 (高电平时间), 单位: 微秒 (us)
volatile unsigned long pulsePeriod = 0;    // 周期, 单位: 微秒 (us)
volatile unsigned long pulseStartTime = 0; // 用于记录脉冲开始的时间
volatile uint8_t pulseState = 0;          // 状态机: 0 = 等待上升沿, 1 = 等待下降沿

void setup() {
  // 初始化串口，用于打印结果
  Serial.begin(115200);
  Serial.println("PWM Input Measurement on D5");

  // 配置脉冲输入引脚为输入
  pinMode(PWM_INPUT_PIN, INPUT);

  // --- 配置 Timer1 用于脉冲输入 ---
  
  // 1. 重置 Timer1 的控制寄存器
  TCCR1A = 0;
  TCCR1B = 0;
  
  // 2. 设置噪声消除器
  // 通过设置 ICNC1 (Input Capture Noise Canceler) 位为 1
  // 这会忽略输入引脚上短于 4 个时钟周期的脉冲，能有效过滤噪声
  TCCR1B |= (1 << ICNC1);
  
  // 3. 设置脉冲输入触发边沿
  // 我们先设置为上升沿触发
  // ICES1 (Input Capture Edge Select) = 0 -> 下降沿触发
  // ICES1 (Input Capture Edge Select) = 1 -> 上升沿触发
  TCCR1B |= (1 << ICES1);
  
  // 4. 启用定时器溢出中断 (可选，但推荐用于检测信号丢失)
  // 如果信号周期太长，超过定时器最大值，溢出中断可以帮助我们处理
  TIMSK1 |= (1 << TOIE1);
  
  // 5. 启用脉冲输入中断
  // 当捕获事件发生时，触发中断
  TIMSK1 |= (1 << ICIE1);
  
  // 6. 启动 Timer1，设置预分频器
  // 我们需要设置预分频器，使得定时器的计数频率与我们的测量单位匹配
  // Arduino Mega 2560 时钟频率为 16MHz
  // CS12, CS11, CS10 bits:
  // 001 -> 无预分频 (16MHz) -> 计数器每增加 1 代表 1/16us = 0.0625us
  // 010 -> 8分频 (2MHz)  -> 计数器每增加 1 代表 1/2us = 0.5us
  // 011 -> 64分频 (250kHz) -> 计数器每增加 1 代表 1/0.25us = 4us
  // 100 -> 256分频 (62.5kHz) -> 计数器每增加 1 代表 1/0.0625us = 16us
  // 101 -> 1024分频 (15625Hz) -> 计数器每增加 1 代表 1/0.015625us = 64us
  
  // 为了方便计算，我们选择 8分频，这样每个计数代表 0.5us
  TCCR1B |= (1 << CS11);
  
  // 启用全局中断
  sei();
}

void loop() {
  // 为了不在主循环中频繁打印，我们可以每隔一段时间打印一次
  static unsigned long lastPrintTime = 0;
  
  if (millis() - lastPrintTime > 100) { // 每100ms打印一次
    lastPrintTime = millis();
    
    // 禁用中断，以安全地读取多字节的 volatile 变量
    uint8_t oldSREG = SREG;
    cli();
    
    // 创建局部变量来存储 volatile 变量的值
    unsigned long highTime = pulseHighTime;
    unsigned long period = pulsePeriod;
    
    // 恢复中断状态
    SREG = oldSREG;
    
    // 打印结果
    // 注意：我们将定时器计数值乘以 0.5 来得到微秒，因为我们设置了8分频
    Serial.print("Pulse Width: ");
    Serial.print(highTime * 0.5);
    Serial.print(" us | Period: ");
    Serial.print(period * 0.5);
    Serial.print(" us | Frequency: ");
    
    if (period > 0) {
      // 频率 = 1 / 周期(秒)
      double frequency = 1000000.0 / (period * 0.5);
      Serial.print(frequency, 2); // 打印两位小数
      Serial.println(" Hz");
    } else {
      Serial.println(" N/A");
    }
  }
}

// Timer1 输入捕获中断服务程序
ISR(TIMER1_CAPT_vect) {
  // 读取捕获的定时器值
  unsigned int capturedValue = ICR1;
  
  if (pulseState == 0) {
    // 状态0：我们正在等待上升沿
    // 记录上升沿发生的时间
    pulseStartTime = capturedValue;
    
    // 计算周期 (当前上升沿 - 上一个上升沿)
    // 注意：这里需要处理定时器溢出的情况，为简化示例，我们暂时忽略
    // 在实际应用中，应该检查 (capturedValue - pulseStartTime) 是否为负数
    // 如果是，说明发生了溢出，需要加上定时器的最大值 (65535)
    pulsePeriod = capturedValue - pulseStartTime;
    
    // 改变触发边沿为下降沿，以捕获脉宽
    TCCR1B &= ~(1 << ICES1); // 清除 ICES1 位，设置为下降沿触发
    
    // 切换到状态1
    pulseState = 1;
    
  } else {
    // 状态1：我们正在等待下降沿
    // 计算脉宽 (下降沿 - 上升沿)
    pulseHighTime = capturedValue - pulseStartTime;
    
    // 改变触发边沿为上升沿，以捕获下一个周期的开始
    TCCR1B |= (1 << ICES1); // 设置 ICES1 位，设置为上升沿触发
    
    // 切换回状态0
    pulseState = 0;
  }
}

// Timer1 溢出中断服务程序 (可选)
// 如果信号丢失，定时器会一直计数直到溢出
ISR(TIMER1_OVF_vect) {
  // 如果发生溢出，说明信号周期太长或者信号丢失
  // 我们可以在这里将周期和脉宽清零，表示信号无效
  pulsePeriod = 0;
  pulseHighTime = 0;
  // 确保状态机在正确的初始状态
  pulseState = 0;
  TCCR1B |= (1 << ICES1); // 确保是上升沿触发
}
